package fr.umlv.m2.jee.spij.service.fieldWork.impl;

import fr.umlv.m2.jee.spij.persistence.actor.StudentFieldWork;
import fr.umlv.m2.jee.spij.persistence.actor.TeacherFieldWork;
import fr.umlv.m2.jee.spij.persistence.actor.dao.HibernateStudentFieldWorkDAO;
import fr.umlv.m2.jee.spij.persistence.actor.dao.HibernateTeacherFieldWorkDAO;
import fr.umlv.m2.jee.spij.persistence.education.FieldWork;
import fr.umlv.m2.jee.spij.persistence.education.Unit;
import fr.umlv.m2.jee.spij.persistence.education.dao.HibernateFieldWorkDAO;
import fr.umlv.m2.jee.spij.persistence.education.dao.HibernateUnitDAO;
import fr.umlv.m2.jee.spij.service.fieldWork.DisplayFieldWork;
import fr.umlv.m2.jee.spij.service.fieldWork.IDisplayFieldWorkService;
import fr.umlv.m2.jee.spij.service.ldap.authentification.DisplayActor;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Default service about field works.
 *
 * @author thomas magny
 * @author mathieu mazzolini
 */
@Service("defaultDisplayFieldWorkService")
public class DefaultDisplayFieldWorkService implements IDisplayFieldWorkService {

    @Autowired
    private HibernateFieldWorkDAO fieldWorkDAO;

    @Autowired
    private HibernateStudentFieldWorkDAO studentFieldWorkDAO;

    @Autowired
    private HibernateTeacherFieldWorkDAO teacherFieldWorkDAO;

    @Autowired
    private HibernateUnitDAO unitDAO;

    /**
     * Returns all the field works given for a unit.
     *
     * @param unitID  the unit foreign key.
     * @param context the message context.
     *
     * @return a list of DTO that contains the displayed data.
     *
     * @see fr.umlv.m2.jee.spij.service.fieldWork.IDisplayFieldWorkService#
     *      getAllDisplayFieldWork(long,
     *      org.springframework.binding.message.MessageContext)
     */
    @Override
    @Transactional(readOnly = true)
    public List<DisplayFieldWork> getAllDisplayFieldWork(long unitID, MessageContext context) {
        final Unit unit = unitDAO.findById(unitID);

        final List<FieldWork> fieldWorks = unit.getFieldWorks();
        final List<DisplayFieldWork> displayFieldWorks = new ArrayList<DisplayFieldWork>();

        if (fieldWorks == null) {
            context.addMessage(new MessageBuilder().error().code("fieldworks.empty").build());
        } else {
            for (FieldWork fieldWork : fieldWorks) {
                DisplayFieldWork displayFieldWork = new DisplayFieldWork(fieldWork);
                if (unit != null) {
                    displayFieldWork.setUnitName(unit.getName());
                }
                displayFieldWorks.add(displayFieldWork);
            }
        }
        return displayFieldWorks;
    }

    /**
     * Returns all the fieldwork given for an user and a unit.
     *
     * @param unitId      the unit foreign key.
     * @param currentUser the current user.
     * @param context     the message context.
     *
     * @return a list of DTO that contains the displayed data.
     *
     * @see fr.umlv.m2.jee.spij.service.fieldWork.IDisplayFieldWorkService#
     *      getAllDisplayFieldWorkPerUser(long,
     *      fr.umlv.m2.jee.spij.service.ldap.authentification.DisplayActor,
     *      org.springframework.binding.message.MessageContext)
     */
    @Override
    @Transactional(readOnly = true)
    public List<DisplayFieldWork> getAllDisplayFieldWorkPerUser(final long unitId, DisplayActor currentUser, MessageContext context) {
        if (currentUser.isTeacher() || currentUser.isStudent()) {
            Unit unit = unitDAO.findById(unitId);
            final List<FieldWork> fieldWorkList = fieldWorkDAO.findByLdapActorAndUnitId(unitId, currentUser.getLogin(), currentUser.getType());
            final List<DisplayFieldWork> displayFieldWorkList = new ArrayList<DisplayFieldWork>(fieldWorkList.size());

            if (fieldWorkList.isEmpty()) {
                context.addMessage(new MessageBuilder().error().code("fieldworks.empty").build());
            } else {
                for (FieldWork fieldWork : fieldWorkList) {
                    DisplayFieldWork displayFieldWork = new DisplayFieldWork(fieldWork);
                    if (unit != null) {
                        displayFieldWork.setUnitName(unit.getName());
                    }
                    displayFieldWorkList.add(displayFieldWork);
                }
            }
            return displayFieldWorkList;
        }

        return getAllDisplayFieldWork(unitId, context);
    }

    /**
     * Returns displayed data about specific field work.
     *
     * @param id      the fieldWork primary key
     * @param context the message context.
     *
     * @return the data to display
     *
     * @see fr.umlv.m2.jee.spij.service.fieldWork.IDisplayFieldWorkService#getFieldWorkById(long,
     *      org.springframework.binding.message.MessageContext)
     */
    @Override
    @Transactional(readOnly = true)
    public DisplayFieldWork getFieldWorkById(long id, MessageContext context) {
        final FieldWork fieldWork = fieldWorkDAO.findById(id); // should never return null
        final DisplayFieldWork displayFieldWork = new DisplayFieldWork(fieldWork);  // checks null in the constructor

        if (fieldWork == null) {
            context.addMessage(new MessageBuilder().error().code("fieldwork.empty").build());
        } else {
            if (fieldWork.getUnit() != null) {
                displayFieldWork.setUnitName(unitDAO.findById(fieldWork.getUnit()).getName());
            }
        }
        return displayFieldWork;
    }

    /**
     * Creates a new field work to persist into database. The new field work is
     * assigned to a unit according to the unitId given in argument.
     *
     * @param displayFieldWork contains the data to create the new field work
     * @param unitId           the unit foreign key
     * @param context          the message context
     *
     * @return true whether the fieldWork has been created or false otherwise
     *
     * @see fr.umlv.m2.jee.spij.service.fieldWork.IDisplayFieldWorkService#
     *      createFieldWork(fr.umlv.m2.jee.spij.service.fieldWork.DisplayFieldWork,
     *      long, org.springframework.binding.message.MessageContext)
     */
    @Override
    @Transactional
    public boolean createFieldWork(DisplayFieldWork displayFieldWork,
                                   long unitId, MessageContext context) {

        FieldWork fieldWork = new FieldWork();
        fieldWork.setUnit(unitId);
        fieldWork.setSubject(displayFieldWork.getSubject());
        fieldWork.setContain(displayFieldWork.getContain());
        fieldWork.setCorrection(displayFieldWork.getCorrection());
        fieldWork.setLinkSupport(displayFieldWork.getLinkSupport());
        fieldWork.setLinkCorrection(displayFieldWork.getLinkCorrection());

        Date date = displayFieldWork.getDate();
        DateTime startTime = new DateTime(date.getTime());
        startTime = startTime.plus(displayFieldWork.getStartTime().getTime());
        DateTime endTime = new DateTime(date.getTime());
        endTime = endTime.plus(displayFieldWork.getEndTime().getTime());

        if (startTime.isAfter(endTime)) {
            context.addMessage(new MessageBuilder().error().code("fieldwork.startTime.isAfter.endTime").build());
            return false;
        }

        final Interval duration = new Interval(startTime, endTime);
        fieldWork.setDuration(duration);

        final Date deadLineDate = displayFieldWork.getDeadLineDate();
        final Date deadLineHour = displayFieldWork.getDeadLineHour();
        if (deadLineDate != null && deadLineHour != null) {
            DateTime deadLine = new DateTime(deadLineDate.getTime());
            deadLine = deadLine.plus(deadLineHour.getTime());
            if (endTime.isAfter(deadLine)) {
                context.addMessage(new MessageBuilder().error().code("fieldwork.deadLine.isBefore.endTime").build());
                return false;
            }
            fieldWork.setDeadLine(deadLine);
        } else if (deadLineDate != null) {
            context.addMessage(new MessageBuilder().error().code("fieldwork.deadLine.incomplete").build());
            return false;
        } else if (deadLineHour != null) {
            context.addMessage(new MessageBuilder().error().code("fieldwork.deadLine.incomplete").build());
            return false;
        }

        fieldWorkDAO.persist(fieldWork);
        return true;
    }


    /**
     * Updates a field work with datas given into DisplayFieldWork object
     *
     * @param modifiedFieldWork the modified data
     * @param context           the message context
     *
     * @return true whether the fieldWork has been updated or false otherwise
     *
     * @see fr.umlv.m2.jee.spij.service.fieldWork.IDisplayFieldWorkService#
     *      updateFieldWorkById
     *      (fr.umlv.m2.jee.spij.service.fieldWork.DisplayFieldWork,
     *      org.springframework.binding.message.MessageContext)
     */
    @Override
    @Transactional
    public boolean updateFieldWorkById(final DisplayFieldWork modifiedFieldWork, final MessageContext context) {
        FieldWork fieldWork = fieldWorkDAO.findById(modifiedFieldWork.getId());
        boolean hasBeenModified = false;

        if (fieldWork != null) {
            if (!fieldWork.getSubject().equals(modifiedFieldWork.getSubject())) {
                hasBeenModified = true;
                fieldWork.setSubject(modifiedFieldWork.getSubject());
            }
            if (!fieldWork.getContain().equals(modifiedFieldWork.getContain())) {
                hasBeenModified = true;
                fieldWork.setContain(modifiedFieldWork.getContain());
            }
            if (!fieldWork.getCorrection().equals(modifiedFieldWork.getCorrection())) {
                hasBeenModified = true;
                fieldWork.setCorrection(modifiedFieldWork.getCorrection());
            }
            if (!fieldWork.getLinkSupport().equals(modifiedFieldWork.getLinkSupport())) {
                hasBeenModified = true;
                fieldWork.setLinkSupport(modifiedFieldWork.getLinkSupport());
            }
            if (!fieldWork.getLinkCorrection().equals(modifiedFieldWork.getLinkCorrection())) {
                hasBeenModified = true;
                fieldWork.setLinkCorrection(modifiedFieldWork.getLinkCorrection());
            }

            //Duration update
            Date date = modifiedFieldWork.getDate();
            DateTime startTime = new DateTime(date.getTime());
            startTime = startTime.plus(modifiedFieldWork.getStartTime().getTime());

            DateTime endTime = new DateTime(date.getTime());
            endTime = endTime.plus(modifiedFieldWork.getEndTime().getTime());

            DateTime previousStartDate = fieldWork.getDuration().getStart();
            DateTime previousEndDate = fieldWork.getDuration().getEnd();

            if (startTime.isAfter(endTime)) {
                context.addMessage(new MessageBuilder().error().code("fieldwork.startTime.isAfter.endTime").build());
                return false;
            }

            //Deadline update
            Date deadLineDate = modifiedFieldWork.getDeadLineDate();
            Date deadLineHour = modifiedFieldWork.getDeadLineHour();

            DateTime previousDeadLine = fieldWork.getDeadLine();

            if (deadLineDate != null && deadLineHour != null) {
                DateTime deadLine = new DateTime(deadLineDate.getTime());
                deadLine = deadLine.plus(deadLineHour.getTime());
                if (endTime.isAfter(deadLine)) {
                    context.addMessage(new MessageBuilder().error().code("fieldwork.deadLine.isBefore.endTime").build());
                    return false;
                }
                if (!deadLine.equals(previousDeadLine)) {
                    hasBeenModified = true;
                    fieldWork.setDeadLine(deadLine);
                }
            } else if (deadLineDate != null) {
                context.addMessage(new MessageBuilder().error().code("fieldwork.deadLine.incomplete").build());
                return false;
            } else if (deadLineHour != null) {
                context.addMessage(new MessageBuilder().error().code("fieldwork.deadLine.incomplete").build());
                return false;
            } else {
                if (previousDeadLine != null) {
                    hasBeenModified = true;
                    fieldWork.setDeadLine(null);
                }
            }

            if (!previousStartDate.equals(startTime) || !previousEndDate.equals(endTime)) {
                hasBeenModified = true;
                fieldWork.setDuration(new Interval(startTime, endTime));
            }

            fieldWorkDAO.merge(fieldWork);
        }

        if (!hasBeenModified) {
            context.addMessage(new MessageBuilder().error().code("updateFieldWorkFail").build());
        }

        return hasBeenModified;
    }

    /**
     * Removes a field work according to its primary key.
     *
     * @param id      the primary key
     * @param context the message context
     *
     * @return true whether the fieldWork has been well removed or false
     *         otherwise.
     *
     * @see fr.umlv.m2.jee.spij.service.fieldWork.IDisplayFieldWorkService#
     *      deleteFieldWorkById(long,
     *      org.springframework.binding.message.MessageContext)
     */
    @Override
    @Transactional
    public boolean deleteFieldWorkById(final long id, final MessageContext context) {
        FieldWork fieldWork = fieldWorkDAO.findById(id);
        List<StudentFieldWork> students = fieldWork.getStudents();
        for (StudentFieldWork studentFieldWork : students) {
            studentFieldWorkDAO.remove(studentFieldWork);
            studentFieldWorkDAO.flush();
        }
        List<TeacherFieldWork> teachers = fieldWork.getTeachers();
        for (TeacherFieldWork teacherFieldWork : teachers) {
            teacherFieldWorkDAO.remove(teacherFieldWork);
            teacherFieldWorkDAO.flush();
        }

        fieldWorkDAO.remove(fieldWork);
        fieldWorkDAO.flush();
        return true;
    }
}
